/******************************************************************************* * Copyright (c) 2009-2015 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * * Tijs van der Storm - Tijs.van.der.Storm@cwi.nl * * Paul Klint - Paul.Klint@cwi.nl - CWI * * Mark Hills - Mark.Hills@cwi.nl (CWI) * * Arnold Lankamp - Arnold.Lankamp@cwi.nl * * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI *******************************************************************************/ package org.rascalmpl.values.uptr; import java.io.CharArrayWriter; import java.io.IOException; import java.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.fusesource.jansi.Ansi; import org.fusesource.jansi.Ansi.Attribute; import org.fusesource.jansi.Ansi.Color; import org.rascalmpl.interpreter.asserts.ImplementationError; import org.rascalmpl.interpreter.utils.LimitedResultWriter; import org.rascalmpl.interpreter.utils.LimitedResultWriter.IOLimitReachedException; import org.rascalmpl.value.IConstructor; import org.rascalmpl.value.IInteger; import org.rascalmpl.value.IList; import org.rascalmpl.value.IListWriter; import org.rascalmpl.value.ISet; import org.rascalmpl.value.ISourceLocation; import org.rascalmpl.value.IValue; import org.rascalmpl.value.exceptions.FactTypeUseException; import org.rascalmpl.values.ValueFactoryFactory; import org.rascalmpl.values.uptr.visitors.TreeVisitor; public class TreeAdapter { public static final String NORMAL = "Normal"; public static final String TYPE = "Type"; public static final String IDENTIFIER = "Identifier"; public static final String VARIABLE = "Variable"; public static final String CONSTANT = "Constant"; public static final String COMMENT = "Comment"; public static final String TODO = "Todo"; public static final String QUOTE = "Quote"; public static final String META_AMBIGUITY = "MetaAmbiguity"; public static final String META_VARIABLE = "MetaVariable"; public static final String META_KEYWORD = "MetaKeyword"; public static final String META_SKIPPED = "MetaSkipped"; public static final String NONTERMINAL_LABEL = "NonterminalLabel"; public static final String RESULT = "Result"; public static final String STDOUT = "StdOut"; public static final String STDERR = "StdErr"; private TreeAdapter() { super(); } public static boolean isTree(IConstructor cons) { return cons instanceof ITree; } public static boolean isAppl(ITree tree) { return tree.isAppl(); } private static int findLabelPosition(ITree tree, String label) { if (!TreeAdapter.isAppl(tree)) { throw new ImplementationError("can not call getArg on a non-tree"); } IConstructor prod = TreeAdapter.getProduction(tree); if (!ProductionAdapter.isDefault(prod)) { return -1; } IList syms = ProductionAdapter.getSymbols(prod); for (int i = 0; i < syms.length(); i++) { IConstructor sym = (IConstructor) syms.get(i); while (SymbolAdapter.isConditional(sym)) { sym = SymbolAdapter.getSymbol(sym); } if (SymbolAdapter.isLabel(sym)) { if (SymbolAdapter.getLabel(sym).equals(label)) { return i; } } } return -1; } public static ITree getArg(ITree tree, String label) { return (ITree) getArgs(tree).get(findLabelPosition(tree, label)); } public static ITree setArg(ITree tree, String label, IConstructor newArg) { return setArgs(tree, getArgs(tree).put(findLabelPosition(tree, label), newArg)); } public static boolean isAmb(ITree tree) { return tree.isAmb(); } public static boolean isTop(ITree tree) { return SymbolAdapter.isStartSort(getType(tree)); } public static boolean isChar(ITree tree) { return tree.isChar(); } public static boolean isCycle(ITree tree) { return tree.isCycle(); } public static boolean isComment(ITree tree) { IConstructor treeProd = getProduction(tree); if (treeProd != null) { String treeProdCategory = ProductionAdapter.getCategory(treeProd); if (treeProdCategory != null && treeProdCategory.equals("Comment")) return true; } return false; } public static IConstructor getProduction(ITree tree) { return tree.getProduction(); } public static IConstructor getType(ITree tree) { if (isAppl(tree)) { IConstructor sym = ProductionAdapter.getType(getProduction(tree)); if (SymbolAdapter.isStarList(sym) && !getArgs(tree).isEmpty()) { sym = SymbolAdapter.starToPlus(sym); } return sym; } else if (isCycle(tree)) { return (IConstructor) tree.get("symbol"); } else if (isAmb(tree)) { return getType((ITree) getAlternatives(tree).iterator().next()); } throw new ImplementationError("ITree does not have a type"); } public static String getSortName(ITree tree) throws FactTypeUseException { return ProductionAdapter.getSortName(getProduction(tree)); } /* (non-Javadoc) * @see org.rascalmpl.values.uptr.ProductionAdapter#getConstructorName(IConstructor tree) */ public static String getConstructorName(ITree tree) { return ProductionAdapter.getConstructorName(getProduction(tree)); } public static boolean isProduction(ITree tree, String sortName, String consName) { IConstructor prod = getProduction(tree); return ProductionAdapter.getSortName(prod).equals(sortName) && ProductionAdapter.getConstructorName(prod).equals(consName); } public static boolean isContextFree(ITree tree) { return isAppl(tree) ? ProductionAdapter .isContextFree(getProduction(tree)) : false; } public static boolean isList(ITree tree) { return isAppl(tree) ? ProductionAdapter.isList(getProduction(tree)) : false; } public static boolean isOpt(ITree tree) { return isAppl(tree) ? ProductionAdapter.isOpt(getProduction(tree)) : false; } public static IList getArgs(ITree tree) { if (isAppl(tree)) { return (IList) tree.get("args"); } throw new ImplementationError("Node has no args: " + tree.getName()); } public static ITree setArgs(ITree tree, IList args) { if (isAppl(tree)) { return (ITree) tree.set("args", args); } throw new ImplementationError("Node has no args: " + tree.getName()); } public static ITree setProduction(ITree tree, IConstructor prod) { if (isAppl(tree)) { return (ITree) tree.set("prod", prod); } throw new ImplementationError("Node has no args: " + tree.getName()); } public static boolean isLiteral(ITree tree) { return isAppl(tree) ? ProductionAdapter.isLiteral(getProduction(tree)) : false; } public static IList getListASTArgs(ITree tree) { if (!isList(tree)) { throw new ImplementationError( "This is not a context-free list production: " + tree); } IList children = getArgs(tree); IListWriter writer = ValueFactoryFactory.getValueFactory().listWriter(); for (int i = 0; i < children.length(); ++i) { IValue kid = children.get(i); writer.append(kid); // skip layout and/or separators if (isSeparatedList(tree)) { i += getSeparatorCount(tree); } else { ++i; } } return writer.done(); } public static int getSeparatorCount(ITree tree) { IConstructor nt = ProductionAdapter.getType(getProduction(tree)); return SymbolAdapter.isSepList(nt) ? SymbolAdapter.getSeparators(nt).length() : 0; } public static boolean isLexical(ITree tree) { return isAppl(tree) ? ProductionAdapter.isLexical(getProduction(tree)) : false; } public static boolean isSort(ITree tree) { return isAppl(tree) ? ProductionAdapter.isSort(getProduction(tree)) : false; } public static boolean isLayout(ITree tree) { return isAppl(tree) ? ProductionAdapter.isLayout(getProduction(tree)) : false; } public static boolean isSeparatedList(ITree tree) { return isAppl(tree) ? isList(tree) && ProductionAdapter.isSeparatedList(getProduction(tree)) : false; } public static IList getASTArgs(ITree tree) { if (SymbolAdapter.isStartSort(TreeAdapter.getType(tree))) { return getArgs(tree).delete(0).delete(1); } if (isLexical(tree)) { throw new ImplementationError("This is not a context-free production: " + tree); } IList children = getArgs(tree); IListWriter writer = ValueFactoryFactory.getValueFactory().listWriter(); for (int i = 0; i < children.length(); i++) { ITree kid = (ITree) children.get(i); if (!isLiteral(kid) && !isCILiteral(kid)) { writer.append(kid); } // skip layout i++; } return writer.done(); } public static boolean isCILiteral(ITree tree) { return isAppl(tree) ? ProductionAdapter .isCILiteral(getProduction(tree)) : false; } public static ISet getAlternatives(ITree tree) { if (isAmb(tree)) { return (ISet) tree.get("alternatives"); } throw new ImplementationError("Node has no alternatives"); } public static ISourceLocation getLocation(ITree tree) { return (ISourceLocation) tree.asAnnotatable().getAnnotation(RascalValueFactory.Location); } public static ITree setLocation(ITree tree, ISourceLocation loc) { return (ITree) tree.asAnnotatable().setAnnotation(RascalValueFactory.Location, loc); } public static int getCharacter(ITree tree) { return ((IInteger) tree.get("character")).intValue(); } private static class Unparser extends TreeVisitor<IOException> { protected final Writer fStream; private final boolean fHighlight; private final Map<String,Ansi> ansiOpen = new HashMap<>(); private final Map<String,Ansi> ansiClose = new HashMap<>(); public Unparser(Writer stream, boolean highlight) { fStream = stream; fHighlight = highlight; ansiOpen.put(NORMAL, Ansi.ansi().a(Attribute.ITALIC_OFF).a(Attribute.INTENSITY_BOLD_OFF) .fg(Color.DEFAULT).fgBright(Color.DEFAULT)); ansiClose.put(NORMAL, Ansi.ansi().a(Attribute.ITALIC_OFF).a(Attribute.INTENSITY_BOLD_OFF) .fg(Color.DEFAULT).fgBright(Color.DEFAULT)); ansiOpen.put(NONTERMINAL_LABEL, Ansi.ansi().a(Attribute.ITALIC).fg(Color.CYAN)); ansiClose.put(NONTERMINAL_LABEL, Ansi.ansi().a(Attribute.ITALIC_OFF).fg(Color.DEFAULT)); ansiOpen.put(META_KEYWORD, Ansi.ansi().fg(Color.MAGENTA)); ansiClose.put(META_KEYWORD, Ansi.ansi().fg(Color.DEFAULT)); ansiOpen.put(META_VARIABLE, Ansi.ansi().a(Attribute.ITALIC).fgBright(Color.GREEN)); ansiClose.put(META_VARIABLE, Ansi.ansi().a(Attribute.ITALIC_OFF).fgBright(Color.DEFAULT)); ansiOpen.put(META_AMBIGUITY, Ansi.ansi().a(Attribute.INTENSITY_BOLD).fgBright(Color.RED)); ansiClose.put(META_AMBIGUITY, Ansi.ansi().a(Attribute.INTENSITY_BOLD_OFF).fgBright(Color.DEFAULT)); ansiOpen.put(META_SKIPPED, Ansi.ansi().bgBright(Color.RED)); ansiClose.put(META_SKIPPED, Ansi.ansi().bgBright(Color.WHITE)); ansiOpen.put(COMMENT, Ansi.ansi().a(Attribute.ITALIC).fg(Color.GREEN)); ansiClose.put(COMMENT, Ansi.ansi().a(Attribute.ITALIC_OFF).fg(Color.DEFAULT)); } /** * This Visitor tries to find if this tree contains a cycle, without going in to the amb parts */ private static class CycleDetector extends TreeVisitor<IOException> { public boolean result = false; @Override public ITree visitTreeCycle(ITree arg) throws IOException { result = true; return arg; } @Override public ITree visitTreeAppl(ITree arg) throws IOException { if (!result) { IList children = (IList) arg.get("args"); for (IValue child : children) { child.accept(this); if (result) { break; } } } return arg; } @Override public ITree visitTreeAmb(ITree arg) throws IOException { // don't go into other amb trees with cycles return arg; } @Override public ITree visitTreeChar(ITree arg) throws IOException { return arg; } public static boolean detect(ITree tree) throws IOException { CycleDetector look = new CycleDetector(); tree.accept(look); return look.result; } } public ITree visitTreeAmb(ITree arg) throws IOException { ISet alts = TreeAdapter.getAlternatives(arg); if (alts.isEmpty()) { return arg; } Iterator<IValue> alternatives = alts.iterator(); // do not try to print the alternative with the cycle in it. // so lets try to find the tree without the cycle ITree tree = (ITree)alternatives.next(); while (alternatives.hasNext() && CycleDetector.detect(tree) ) { tree = (ITree)alternatives.next(); } tree.accept(this); return arg; } public ITree visitTreeCycle(ITree arg) throws IOException { return arg; } public ITree visitTreeChar(ITree arg) throws IOException { fStream.write(Character.toChars(((IInteger) arg.get("character")).intValue())); return arg; } public ITree visitTreeAppl(ITree arg) throws IOException { boolean reset = false; String category = null; if (fHighlight) { IConstructor prod = TreeAdapter.getProduction(arg); category = ProductionAdapter.getCategory(prod); if (category == null) { if ((TreeAdapter.isLiteral(arg) || TreeAdapter.isCILiteral(arg))) { category = META_KEYWORD; for (IValue child : TreeAdapter.getArgs(arg)) { int c = TreeAdapter.getCharacter((ITree) child); if (c != '-' && !Character.isJavaIdentifierPart(c)){ category = null; } } } } if (category != null) { Ansi code = ansiOpen.get(category); if (code != null) { fStream.write(code.toString()); reset = true; } } } IList children = (IList) arg.get("args"); for (IValue child : children) { child.accept(this); } if (fHighlight && reset) { Ansi code = ansiClose.get(category); if (code != null) { fStream.write(code.toString()); } } return arg; } } private static class UnparserWithFocus extends Unparser { private ISourceLocation focus; private final String BACKGROUND_ON = Ansi.ansi().bgBright(Color.CYAN).toString(); private final String BACKGROUND_OFF = Ansi.ansi().bg(Color.DEFAULT).toString(); private boolean insideFocus = false; public UnparserWithFocus(Writer stream, ISourceLocation focus) { super(stream, true); this.focus = focus; } @Override public ITree visitTreeChar(ITree arg) throws IOException { char[] chars = Character.toChars(((IInteger) arg.get("character")).intValue()); if(insideFocus){ for(int i = 0; i < chars.length; i++){ if(chars[i] == '\n'){ fStream.write(BACKGROUND_OFF); fStream.write(chars[i]); fStream.write(BACKGROUND_ON); } else { fStream.write(chars[i]); } } } else { fStream.write(Character.toChars(((IInteger) arg.get("character")).intValue())); } return arg; } @Override public ITree visitTreeAppl(ITree arg) throws IOException { ISourceLocation argLoc = getLocation(arg); if(argLoc != null && argLoc.getOffset() == focus.getOffset() && argLoc.getLength() == focus.getLength()){ fStream.write(BACKGROUND_ON); insideFocus = true; super.visitTreeAppl(arg); insideFocus = false; fStream.write(BACKGROUND_OFF); } else { super.visitTreeAppl(arg); } return arg; } } public static IConstructor locateLexical(ITree tree, int offset) { ISourceLocation l = TreeAdapter.getLocation(tree); if (l == null) { throw new IllegalArgumentException( "locate assumes position information on the tree"); } if (TreeAdapter.isLexical(tree)) { if (l.getOffset() <= offset && offset < l.getOffset() + l.getLength()) { return tree; } return null; } if (TreeAdapter.isAmb(tree)) { return null; } if (TreeAdapter.isAppl(tree)) { IList children = TreeAdapter.getASTArgs(tree); for (IValue child : children) { ISourceLocation childLoc = TreeAdapter.getLocation((ITree) child); if (childLoc == null) { continue; } if (childLoc.getOffset() <= offset && offset < childLoc.getOffset() + childLoc.getLength()) { IConstructor result = locateLexical((ITree) child, offset); if (result != null) { return result; } break; } } if (l.getOffset() <= offset && l.getOffset() + l.getLength() >= offset) { return tree; } } return null; } /** * This finds the most specific (smallest) annotated tree which has its yield around the given offset. */ public static ITree locateAnnotatedTree(ITree tree, String label, int offset) { ISourceLocation l = TreeAdapter.getLocation(tree); if (l == null) { throw new IllegalArgumentException( "locate assumes position information on the tree"); } if (TreeAdapter.isAmb(tree)) { if (tree.asAnnotatable().hasAnnotation(label)) { return tree; } return null; } if (TreeAdapter.isAppl(tree) && !TreeAdapter.isLexical(tree)) { IList children = TreeAdapter.getArgs(tree); //TreeAdapter.getASTArgs(tree); for (IValue child : children) { ISourceLocation childLoc = TreeAdapter.getLocation((ITree) child); if (childLoc == null) { continue; } if (childLoc.getOffset() <= offset && offset < childLoc.getOffset() + childLoc.getLength()) { ITree result = locateAnnotatedTree((ITree) child, label, offset); if (result != null) { return result; } } } } if (l.getOffset() <= offset && l.getOffset() + l.getLength() >= offset) { if (tree.asAnnotatable().hasAnnotation(label)) { return tree; } } return null; } public static void unparse(IConstructor tree, Writer stream) throws FactTypeUseException, IOException { unparse(tree, false, stream); } public static void unparse(IConstructor tree, boolean highlight, Writer stream) throws IOException, FactTypeUseException { if (tree instanceof ITree) { tree.accept(new Unparser(stream, highlight)); } else { throw new ImplementationError("Can not unparse this " + tree + " (type = " + tree.getType() + ")") ; } } public static void unparseWithFocus(IConstructor tree, Writer stream, ISourceLocation focus) throws IOException, FactTypeUseException { if (tree instanceof ITree) { tree.accept(new UnparserWithFocus(stream, focus)); } else { throw new ImplementationError("Can not unparse this " + tree + " (type = " + tree.getType() + ")") ; } } public static String yield(IConstructor tree, boolean highlight, int limit) throws FactTypeUseException { Writer stream = new LimitedResultWriter(limit); try { unparse(tree, highlight, stream); return stream.toString(); } catch (IOLimitReachedException e) { return stream.toString(); } catch (IOException e) { throw new ImplementationError("Method yield failed", e); } } public static String yield(IConstructor tree, int limit) throws FactTypeUseException { return yield(tree, false, limit); } public static String yield(IConstructor tree) throws FactTypeUseException { return yield(tree, false); } public static String yield(IConstructor tree, boolean highlight) throws FactTypeUseException { try { Writer stream = new CharArrayWriter(); unparse(tree, highlight, stream); return stream.toString(); } catch (IOException e) { throw new ImplementationError("Method yield failed", e); } } public static void yield(IConstructor tree, Writer out) throws IOException { unparse(tree, out); } public static void yield(IConstructor tree, boolean highlight, Writer out) throws IOException { unparse(tree, highlight, out); } public static boolean isInjectionOrSingleton(ITree tree) { IConstructor prod = getProduction(tree); if (isAppl(tree)) { if (ProductionAdapter.isDefault(prod)) { return ProductionAdapter.getSymbols(prod).length() == 1; } else if (ProductionAdapter.isList(prod)) { return getArgs(tree).length() == 1; } } return false; } public static boolean isAmbiguousList(ITree tree) { if (isAmb(tree)) { ITree first = (ITree) getAlternatives(tree).iterator().next(); if (isList(first)) { return true; } } return false; } public static boolean isNonEmptyStarList(ITree tree) { if (isAppl(tree)) { IConstructor prod = getProduction(tree); if (ProductionAdapter.isList(prod)) { IConstructor sym = ProductionAdapter.getType(prod); if (SymbolAdapter.isIterStar(sym) || SymbolAdapter.isIterStarSeps(sym)) { return getArgs(tree).length() > 0; } } } return false; } public static boolean isPlusList(ITree tree) { if (isAppl(tree)) { IConstructor prod = getProduction(tree); if (ProductionAdapter.isList(prod)) { IConstructor sym = ProductionAdapter.getType(prod); if (SymbolAdapter.isIterPlus(sym) || SymbolAdapter.isIterPlusSeps(sym)) { return true; } } } return false; } /** * @return true if the tree does not have any characters, it's just an empty * derivation */ public static boolean isEpsilon(ITree tree) { if (isAppl(tree)) { for (IValue arg : getArgs(tree)) { boolean argResult = isEpsilon((ITree) arg); if (argResult == false) { return false; } } return true; } if (isAmb(tree)) { return isEpsilon((ITree) getAlternatives(tree).iterator() .next()); } if (isCycle(tree)) { return true; } // is a character return false; } public static IList searchCategory(ITree tree, String category) { IListWriter writer = ValueFactoryFactory.getValueFactory().listWriter(); if (isAppl(tree)) { String s = ProductionAdapter.getCategory(getProduction(tree)); if (s == category) writer.append(tree); else { IList z = getArgs(tree); for (IValue q : z) { if (!(q instanceof IConstructor)) continue; IList p = searchCategory((ITree) q, category); writer.appendAll(p); } } } return writer.done(); } public static boolean isRascalLexical(ITree tree) { return SymbolAdapter.isLex(getType(tree)); } public static IConstructor locateDeepestContextFreeNode(ITree tree, int offset) { ISourceLocation l = TreeAdapter.getLocation(tree); if (l == null) { throw new IllegalArgumentException( "locate assumes position information on the tree"); } if (TreeAdapter.isLexical(tree)) { if (l.getOffset() <= offset && offset < l.getOffset() + l.getLength()) { return tree; } return null; } if (TreeAdapter.isAmb(tree)) { return null; } if (TreeAdapter.isAppl(tree)) { IList children = TreeAdapter.getASTArgs(tree); for (IValue child : children) { ISourceLocation childLoc = TreeAdapter .getLocation((ITree) child); if (childLoc == null) { continue; } if (childLoc.getOffset() <= offset && offset < childLoc.getOffset() + childLoc.getLength()) { IConstructor result = locateDeepestContextFreeNode((ITree) child, offset); if (result != null) { return result; } break; } } if (l.getOffset() <= offset && l.getOffset() + l.getLength() >= offset) { return tree; } } return null; } public static boolean isEmpty(ITree kid) { return isAppl(kid) && SymbolAdapter.isEmpty(ProductionAdapter.getType(getProduction(kid))); } public static int getCycleLength(ITree tree) { return new Integer(((IInteger) tree.get("cycleLength")).getStringRepresentation()).intValue(); } public static IConstructor getCycleType(ITree tree) { return (IConstructor) tree.get("symbol"); } public static ITree getStartTop(ITree prefix) { return (ITree) getArgs(prefix).get(1); } public static IList getNonLayoutArgs(ITree treeSubject) { IListWriter w = ValueFactoryFactory.getValueFactory().listWriter(); for (IValue v : getArgs(treeSubject)) { if (!TreeAdapter.isLayout((ITree) v)) { w.append(v); } } return w.done(); } }